package com.asm.test;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;

public class LengthFieldBasedFrameDecoderTestServer {
    public static void main(String[] args) throws InterruptedException {
        new ServerBootstrap()
                .group(new NioEventLoopGroup())
                .channel(NioServerSocketChannel.class)
                // 调整服务器端 滑动窗口大小，一般不用设置，在建立连接后系统自动设置，netty默认的是1024
                // 这个窗口大小表示一次OP_READ事件中，用户可从socket内核缓冲区读取数据的长度，最多可读取该参数的2倍，
                // 即使byteBuf没有读满也返回。每次调用java nio原生socketChannel.read(byteBuf)时，
                // 就会将socket内核缓冲区的数据读入byteBuf中，每次读取字节数受两个参数限制：一个是byteBuf可写字节数，如果
                // 将byteBuf写满了，但是内核缓冲区中的数据还没读完，可以循环调用该行代码读取内核缓冲区；另一个是此处说的窗口
                // 大小，即SO_RCVBUF大小，该参数限制了该行代码一次内从内核缓冲区读取的最大字节数为SO_RCVBUF*2 。
                // 但是第二次处理OP_READ时，SO_RCVBUF会扩容到最近的2^n。
                .option(ChannelOption.SO_RCVBUF, 13)
                // 调整 netty 的接收缓冲区大小（ByteBuf） AdaptiveRecvByteBufAllocator(最小值, 初始值, 最大值)
                // AbstractNioByteChannel.read方法中，读取内核数据时，暂时创建的用户缓冲区byteBuf的大小，
                // 默认使用PooledByteBufAllocator，byteBuf类型为PooledUnsafeDirectByteBuf，大小1024
                .childOption(ChannelOption.RCVBUF_ALLOCATOR, new AdaptiveRecvByteBufAllocator(18, 18, 18))
                .childHandler(new ChannelInitializer<SocketChannel>() { // SocketChannel建立后被调用一次
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                        /**
                         * 报文格式
                         * |+0x000D+|+“12345678”+|
                         * 只有两部分，
                         * 第一部分为头部，4字节，int类型，表示报文体长度；
                         * 第二部分为报文体，即实际数据字节。
                         */
                        ch.pipeline().addLast(new MyLengthFieldBasedFrameDecoder(
                                16,  // 指最大包长度，如果Netty最终生成的数据包超过这个长度，Netty就会扩容
                                0, // 指明Length的偏移位，在这里应该是0
                                4, // int类型，4字节
                                0, // 当length表示整包长度时，该参数为一个负数，该参数与length相加表示报文体长度，我们规定的length就是指报文体长度，因此该字段设为0
                                0)); // 当前handler向后传递字节序时，忽略整体报文的前多少个字节，只返回剩余字节
                        ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    }
                })
                .bind(8080)
                .sync()
                .channel()
                .closeFuture()
                .sync();

    }

    @Slf4j
    public static class MyLengthFieldBasedFrameDecoder extends LengthFieldBasedFrameDecoder {
        public MyLengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip) {
            super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
        }

        @Override
        protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
            try {
                return super.decode(ctx, in);
            } catch (TooLongFrameException e) {
                // log.error("frame too long", e);
                ctx.channel().close(); // 包超长则直接关闭channel
                throw  e;
            }
        }
    }
}
